/*
Copyright (c) 2009, Shlomy Gantz BlueBrick Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Shlomy Gantz or BlueBrick Inc. nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY SHLOMY GANTZ/BLUEBRICK INC. ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SHLOMY GANTZ/BLUEBRICK INC. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/**
 * JSGantt component is a UI control that displays gantt charts based by using CSS and HTML
 * @module    jsgantt
 * @title    JSGantt
 */

var JSGantt; if (!JSGantt) JSGantt = {};
var vgantColor;
var vTimeout = 0;
var vBenchTime = new Date().getTime();

/**
 * Creates a task (one row) in gantt object
 * @class TaskItem
 * @namespace JSGantt
 * @constructor
 * @for JSGantt

 * @param pID {Number} Task unique numeric ID
 * @param pName {String} Task Name
 * @param pStart {Date} Task start date/time (not required for pGroup=1 )
 * @param pEnd {Date} Task end date/time, you can set the end time to 12:00 to indicate half-day (not required for pGroup=1 )
 * @param pColor {String} Task bar RGB value
 * @param pLink {String} Task URL, clicking on the task will redirect to this url. Leave empty if you do not with the Task also serve as a link
 * @param pMile {Boolean} Determines whether task is a milestone (1=Yes,0=No)
 * @param pRes {String} Resource to perform the task
 * @param pComp {Number} Percent complete (Number between 0 and 100)
 * @param pGroup {Boolean}
 * @param pParent {Number} ID of the parent task
 * @param pOpen {Boolean}
 * @param pDepend {String} Comma seperated list of IDs this task depends on
 * @param pCaption {String} Caption to be used instead of default caption (Resource).
 * note : you should use setCaption("Caption") in order to display the caption
 * @return void
 */
JSGantt.TaskItem = function(pID, pName, pStart, pEnd, pColor, pLink, pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend, pCaption)
{

	/**
	 * The name of the attribute.
	 * @property vID
	 * @type String
	 * @default pID
	 * @private
	 */
	var vID    = pID;

	/**
	 * @property vName
	 * @type String
	 * @default pName
	 * @private
	 */
	var vName  = pName;

	/**
	 * @property vStart
	 * @type Datetime
	 * @default new Date()
	 * @private
	 */
	var vStart = new Date();

	/**
	 * @property vEnd
	 * @type Datetime
	 * @default new Date()
	 * @private
	 */
	var vEnd   = new Date();

	/**
	 * @property vColor
	 * @type String
	 * @default pColor
	 * @private
	 */
	var vColor = pColor;

	/**
	 * @property vLink
	 * @type String
	 * @default pLink
	 * @private
	 */
	var vLink  = pLink;

	/**
	 * @property vMile
	 * @type Boolean
	 * @default pMile
	 * @private
	 */
	var vMile  = pMile;

	/**
	 * @property vRes
	 * @type String
	 * @default pRes
	 * @private
	 */
	var vRes   = pRes;

	/**
	 * @property vComp
	 * @type Number
	 * @default pComp
	 * @private
	 */
	var vComp  = pComp;

	/**
	 * @property vGroup
	 * @type Boolean
	 * @default pGroup
	 * @private
	 */
	var vGroup = pGroup;

	/**
	 * @property vParent
	 * @type Number
	 * @default pParent
	 * @private
	 */
	var vParent = pParent;

	/**
	 * @property vOpen
	 * @type Boolean
	 * @default pOpen
	 * @private
	 */
	var vOpen   = pOpen;

	/**
	 * @property vDepend
	 * @type String
	 * @default pDepend
	 * @private
	 */
	var vDepend = pDepend;

	/**
	 * @property vCaption
	 * @type String
	 * @default pCaption
	 * @private
	 */
	var vCaption = pCaption;

	/**
	 * @property vDuration
	 * @type Number
	 * @default ''
	 * @private
	 */
	var vDuration = '';

	/**
	 * @property vLevel
	 * @type Number
	 * @default 0
	 * @private
	 */
	var vLevel = 0;

	/**
	 * @property vNumKid
	 * @type Number
	 * @default 0
	 * @private
	 */
	var vNumKid = 0;

	/**
	 * @property vVisible
	 * @type Boolean
	 * @default 0
	 * @private
	 */
	var vVisible  = 1;
	var x1, y1, x2, y2;


	if (vGroup != 1)
	{
		vStart = JSGantt.parseDateStr(pStart,gantg.getDateInputFormat());
		vEnd   = JSGantt.parseDateStr(pEnd,gantg.getDateInputFormat());
	}
	/**
	 * Returns task ID
	 * @method getID
	 * @return {Number}
	 */
	this.getID       = function(){ return vID };
	/**
	 * Returns task name
	 * @method getName
	 * @return {String}
	 */
	this.getName     = function(){ return vName };
	/**
	 * Returns task start date
	 * @method getStart
	 * @return {Datetime}
	 */
	this.getStart    = function(){ return vStart};
	/**
	 * Returns task end date
	 * @method getEnd
	 * @return {Datetime}
	 */    this.getEnd      = function(){ return vEnd  };

	/**
	 * Returns task bar color (i.e. 00FF00)
	 * @method getColor
	 * @return {String}
	 */    this.getColor    = function(){ return vColor};

	/**
	 * Returns task URL (i.e. http://www.jsgantt.com)
	 * @method getLink
	 * @return {String}
	 */    this.getLink     = function(){ return vLink };

	/**
	 * Returns whether task is a milestone (1=Yes,0=No)
	 * @method getMile
	 * @return {Boolean}
	 */    this.getMile     = function(){ return vMile };

	/**
	 * Returns task dependencies as list of values (i.e. 123,122)
	 * @method getDepend
	 * @return {String}
	 */    this.getDepend   = function(){ if(vDepend) return vDepend; else return null };

	/**
	 * Returns task caption (if it exists)
	 * @method getCaption
	 * @return {String}
	 */    this.getCaption  = function(){ if(vCaption) return vCaption; else return ''; };

	/**
	 * Returns task resource name as string
	 * @method getResource
	 * @return {String}
	 */    this.getResource = function(){ if(vRes) return vRes; else return '&nbsp';  };

	/**
	 * Returns task completion percent as numeric value
	 * @method getCompVal
	 * @return {Boolean}
	 */    this.getCompVal  = function(){ if(vComp) return vComp; else return 0; };

	/**
	 * Returns task completion percent as formatted string (##%)
	 * @method getCompStr
	 * @return {String}
	 */    this.getCompStr  = function(){ if(vComp) return vComp+'%'; else return ''; };

	/**
	 * Returns task duration as a fortmatted string based on the current selected format
	 * @method getDuration
	 * @param vFormat {String} selected format (minute,hour,day,week,month)
	 * @return {String}
	 */ 	  this.getDuration = function(vFormat){
		if (vMile)
			vDuration = '-';
		else if (vFormat=='hour')
		{
			tmpPer =  Math.ceil((this.getEnd() - this.getStart()) /  ( 60 * 60 * 1000) );
			if(tmpPer == 1)
				vDuration = '1 小时';
			else
				vDuration = tmpPer + ' 小时';
		}

		else if (vFormat=='minute')
		{
			tmpPer =  Math.ceil((this.getEnd() - this.getStart()) /  ( 60 * 1000) );
			if(tmpPer == 1)
				vDuration = '1 分';
			else
				vDuration = tmpPer + ' 分';
		}

		else { //if(vFormat == 'day') {
			tmpPer =  Math.ceil((this.getEnd() - this.getStart()) /  (24 * 60 * 60 * 1000) + 1);
			if(tmpPer == 1)  vDuration = '1 天';
			else             vDuration = tmpPer + ' 天';
		}

		//else if(vFormat == 'week') {
		//   tmpPer =  ((this.getEnd() - this.getStart()) /  (24 * 60 * 60 * 1000) + 1)/7;
		//   if(tmpPer == 1)  vDuration = '1 Week';
		//   else             vDuration = tmpPer + ' Weeks';
		//}

		//else if(vFormat == 'month') {
		//   tmpPer =  ((this.getEnd() - this.getStart()) /  (24 * 60 * 60 * 1000) + 1)/30;
		//   if(tmpPer == 1) vDuration = '1 Month';
		//   else            vDuration = tmpPer + ' Months';
		//}

		//else if(vFormat == 'quater') {
		//   tmpPer =  ((this.getEnd() - this.getStart()) /  (24 * 60 * 60 * 1000) + 1)/120;
		//   if(tmpPer == 1) vDuration = '1 Qtr';
		//   else            vDuration = tmpPer + ' Qtrs';
		//}
		return( vDuration )
	};

	/**
	 * Returns task parent ID
	 * @method getParent
	 * @return {Number}
	 */      this.getParent   = function(){ return vParent };

	/**
	 * Returns whether task is a group (1=Yes,0=No)
	 * @method getGroup
	 * @return {Number}
	 */    this.getGroup    = function(){ return vGroup };

	/**
	 * Returns whether task is open (1=Yes,0=No)
	 * @method getOpen
	 * @return {Boolean}
	 */    this.getOpen     = function(){ return vOpen };

	/**
	 * Returns task tree level (0,1,2,3...)
	 * @method getLevel
	 * @return {Boolean}
	 */    this.getLevel    = function(){ return vLevel };

	/**
	 * Returns the number of child tasks
	 * @method getNumKids
	 * @return {Number}
	 */    this.getNumKids  = function(){ return vNumKid };
	/**
	 * Returns the X position of the left side of the task bar on the graph (right side)
	 * @method getStartX
	 * @return {Number}
	 */    this.getStartX   = function(){ return x1 };

	/**
	 * Returns the Y position of the top of the task bar on the graph (right side)
	 * @method getStartY
	 * @return {Number}
	 */    this.getStartY   = function(){ return y1 };

	/**
	 * Returns the X position of the right of the task bar on the graph (right side)
	 * @method getEndX
	 * @return {Int}
	 */    this.getEndX     = function(){ return x2 };

	/**
	 * Returns the Y position of the bottom of the task bar on the graph (right side)
	 * @method getEndY
	 * @return {Number}
	 */    this.getEndY     = function(){ return y2 };

	/**
	 * Returns whether task is visible  (1=Yes,0=No)
	 * @method getVisible
	 * @return {Boolean}
	 */    this.getVisible  = function(){ return vVisible };

	/**
	 * Set task dependencies
	 * @method setDepend
	 * @param pDepend {String} A comma delimited list of task IDs the current task depends on.
	 * @return {void}
	 */  this.setDepend   = function(pDepend){ vDepend = pDepend;};

	/**
	 * Set task start date/time
	 * @method setStart
	 * @param pStart {Datetime}
	 * @return {void}
	 */    this.setStart    = function(pStart){ vStart = pStart;};

	/**
	 * Set task end date/time
	 * @method setEnd
	 * @param pEnd {Datetime}
	 * @return {void}
	 */    this.setEnd      = function(pEnd)  { vEnd   = pEnd;  };

	/**
	 * Set task tree level (0,1,2,3...)
	 * @method setLevel
	 * @param pLevel {Number}
	 * @return {void}
	 */    this.setLevel    = function(pLevel){ vLevel = pLevel;};

	/**
	 * Set Number of children for the task
	 * @method setNumKid
	 * @param pNumKid {Number}
	 * @return {void}
	 */    this.setNumKid   = function(pNumKid){ vNumKid = pNumKid;};

	/**
	 * Set task completion percentage
	 * @method setCompVal
	 * @param pCompVal {Number}
	 * @return {void}
	 */    this.setCompVal  = function(pCompVal){ vComp = pCompVal;};

	/**
	 * Set a task bar starting position (left)
	 * @method setStartX
	 * @param pX {Number}
	 * @return {void}
	 */    this.setStartX   = function(pX) {x1 = pX; };

	/**
	 * Set a task bar starting position (top)
	 * @method setStartY
	 * @param pY {Number}
	 * @return {String}
	 */    this.setStartY   = function(pY) {y1 = pY; };

	/**
	 * Set a task bar starting position (right)
	 * @method setEndX
	 * @param pX {Number}
	 * @return {String}
	 */    this.setEndX     = function(pX) {x2 = pX; };

	/**
	 * Set a task bar starting position (bottom)
	 * @method setEndY
	 * @param pY {Number}
	 * @return {String}
	 */    this.setEndY     = function(pY) {y2 = pY; };

	/**
	 * Set task open/closed
	 * @method setOpen
	 * @param pOpen {Boolean}
	 * @return {void}
	 */    this.setOpen     = function(pOpen) {vOpen = pOpen; };

	/**
	 * Set task visibility
	 * @method setVisible
	 * @param pVisible {Boolean}
	 * @return {void}
	 */    this.setVisible  = function(pVisible) {vVisible = pVisible; };

};


/**
 * Creates the gant chart. for example:

 <p>var g = new JSGantt.GanttChart('g',document.getElementById('GanttChartDIV'), 'day');</p>

 var g = new JSGantt.GanttChart( - assign the gantt chart to a javascript variable called 'g'
 'g' - the name of the variable that was just assigned (will be used later so that gantt object can reference itself)
 document.getElementById('GanttChartDIV') - reference to the DIV that will hold the gantt chart
 'day' - default format will be by day

 *
 * @class GanttChart
 * @param pGanttVar {String} the name of the gantt chart variable
 * @param pDiv {String} reference to the DIV that will hold the gantt chart
 * @param pFormat {String} default format (minute,hour,day,week,month,quarter)
 * @return void
 */

JSGantt.GanttChart =  function(pGanttVar, pDiv, pFormat)
{
	/**
	 * The name of the gantt chart variable
	 * @property vGanttVar
	 * @type String
	 * @default pGanttVar
	 * @private
	 */ var vGanttVar = pGanttVar;
	/**
	 * The name of the gantt chart DIV
	 * @property vDiv
	 * @type String
	 * @default pDiv
	 * @private
	 */  var vDiv      = pDiv;
	/**
	 * Selected format (minute,hour,day,week,month)
	 * @property vFormat
	 * @type String
	 * @default pFormat
	 * @private
	 */ var vFormat   = pFormat;
	/**
	 * Show resource column
	 * @property vShowRes
	 * @type Number
	 * @default 1
	 * @private
	 */ var vShowRes  = 1;
	/**
	 * Show duration column
	 * @property vShowDur
	 * @type Number
	 * @default 1
	 * @private
	 */ var vShowDur  = 1;
	/**
	 * Show percent complete column
	 * @property vShowComp
	 * @type Number
	 * @default 1
	 * @private
	 */ var vShowComp = 1;
	/**
	 * Show start date column
	 * @property vShowStartDate
	 * @type Number
	 * @default 1
	 * @private
	 */ var vShowStartDate = 1;
	/**
	 * Show end date column
	 * @property vShowEndDate
	 * @type Number
	 * @default 1
	 * @private
	 */ var vShowEndDate = 1;
	/**
	 * Date input format
	 * @property vDateInputFormat
	 * @type String
	 * @default "mm/dd/yyyy"
	 * @private
	 */var vDateInputFormat = "yy-mm-dd";
	/**
	 * Date display format
	 * @property vDateDisplayFormat
	 * @type String
	 * @default "mm/dd/yy"
	 * @private
	 */var vDateDisplayFormat = "yyyy-mm-dd";

	var vNumUnits  = 0;
	var vCaptionType;
	var vDepId = 1;
	var vTaskList     = new Array();
	var vFormatArr	= new Array("day","week","month","quarter");
	var vQuarterArr   = new Array(1,1,1,2,2,2,3,3,3,4,4,4);
	var vMonthDaysArr = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
	var vMonthArr     = new Array("一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月");
	/**
	 * Set current display format (minute/hour/day/week/month/quarter)
	 * Only the first 4 arguments are used, for example:
	 * <code>
	 * gantg.setFormatArr("day","week","month");
	 * </code>
	 * will show 3 formatting options (day/week/month) at the bottom right of the gantt chart
	 * @method setFormatArr
	 * @return {void}
	 */ this.setFormatArr = function() 	 {
		vFormatArr = new Array();
		for(var i = 0; i < arguments.length; i++) {vFormatArr[i] = arguments[i];}
		if(vFormatArr.length>4){vFormatArr.length=4;}
	};
	/**
	 * Show/Hide resource column
	 * @param pShow {Number} 1=Show,0=Hide
	 * @method setShowRes
	 * @return {void}
	 */ this.setShowRes  = function(pShow) { vShowRes  = pShow; };
	this.clearList  = function() { vTaskList  = []; };
	/**
	 * Show/Hide duration column
	 * @param pShow {Number} 1=Show,0=Hide
	 * @method setShowDur
	 * @return {void}
	 */ this.setShowDur  = function(pShow) { vShowDur  = pShow; };
	/**
	 * Show/Hide completed column
	 * @param pShow {Number} 1=Show,0=Hide
	 * @method setShowComp
	 * @return {void}
	 */ this.setShowComp = function(pShow) { vShowComp = pShow; };
	this.setColor = function(colorlist) {
		vgantColor = colorlist;
	};
	/**
	 * Show/Hide start date column
	 * @param pShow {Number} 1=Show,0=Hide
	 * @method setShowStartDate
	 * @return {void}
	 */ this.setShowStartDate = function(pShow) { vShowStartDate = pShow; };
	/**
	 * Show/Hide end date column
	 * @param pShow {Number} 1=Show,0=Hide
	 * @method setShowEndDate
	 * @return {void}
	 */ this.setShowEndDate = function(pShow) { vShowEndDate = pShow; };
	/**
	 * Overall date input format
	 * @param pShow {String} (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
	 * @method setDateInputFormat
	 * @return {void}
	 */      this.setDateInputFormat = function(pShow) { vDateInputFormat = pShow; };
	/**
	 * Overall date display format
	 * @param pShow {String} (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
	 * @method setDateDisplayFormat
	 * @return {void}
	 */      this.setDateDisplayFormat = function(pShow) { vDateDisplayFormat = pShow; };
	/**
	 * Set gantt caption
	 * @param pType {String}
	 <p>Caption-Displays a custom caption set in TaskItem<br>
	 Resource-Displays task resource<br>
	 Duration-Displays task duration<br>
	 Complete-Displays task percent complete</p>
	 * @method setCaptionType
	 * @return {void}
	 */  this.setCaptionType = function(pType) { vCaptionType = pType };
	/**
	 * Set current display format and redraw gantt chart (minute/hour/day/week/month/quarter)
	 * @param pFormat {String} (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
	 * @method setFormat
	 * @return {void}
	 */ this.setFormat = function(pFormat){
		vFormat = pFormat;
		this.Draw();
	};
	/**
	 * Returns whether resource column is shown
	 * @method getShowRes
	 * @return {Number}
	 */  this.getShowRes  = function(){ return vShowRes };
	/**
	 * Returns whether duration column is shown
	 * @method getShowDur
	 * @return {Number}
	 */  this.getShowDur  = function(){ return vShowDur };
	/**
	 * Returns whether percent complete column is shown
	 * @method getShowComp
	 * @return {Number}
	 */  this.getShowComp = function(){ return vShowComp };
	/**
	 * Returns whether start date column is shown
	 * @method getShowStartDate
	 * @return {Number}
	 */  this.getShowStartDate = function(){ return vShowStartDate };
	/**
	 * Returns whether end date column is shown
	 * @method getShowEndDate
	 * @return {Number}
	 */  this.getShowEndDate = function(){ return vShowEndDate };
	/**
	 * Returns date input format
	 * @method getDateInputFormat
	 * @return {String}
	 */  this.getDateInputFormat = function() { return vDateInputFormat };
	/**
	 * Returns current display format
	 * @method getDateDisplayFormat
	 * @return {String}
	 */  this.getDateDisplayFormat = function() { return vDateDisplayFormat };
	/**
	 * Returns current gantt caption type
	 * @method getCaptionType
	 * @return {String}
	 */  this.getCaptionType = function() { return vCaptionType };
	/**
	 * Calculates X/Y coordinates of a task and sets the Start and End properties of the TaskItem
	 * @method CalcTaskXY
	 * @return {Void}
	 */  this.CalcTaskXY = function ()
	{
		var vList = this.getList();
		var vTaskDiv;
		var vParDiv;
		var vLeft, vTop, vHeight, vWidth;

		for(i = 0; i < vList.length; i++)
		{
			vID = vList[i].getID();
			vTaskDiv = document.getElementById("taskbar_"+vID);
			vBarDiv  = document.getElementById("bardiv_"+vID);
			vParDiv  = document.getElementById("childgrid_"+vID);

			if(vBarDiv) {
				vList[i].setStartX( vBarDiv.offsetLeft );
				vList[i].setStartY( vParDiv.offsetTop+vBarDiv.offsetTop+6 );
				vList[i].setEndX( vBarDiv.offsetLeft + vBarDiv.offsetWidth );
				vList[i].setEndY( vParDiv.offsetTop+vBarDiv.offsetTop+6 );
			};
		};
	};

	/**
	 * Adds a TaskItem to the Gantt object task list array
	 * @method AddTaskItem
	 * @return {Void}
	 */  this.AddTaskItem = function(value)
	{
		vTaskList.push(value);
	};
	/**
	 * Returns task list Array
	 * @method getList
	 * @return {Array}
	 */ this.getList   = function() { return vTaskList };

	/**
	 * Clears dependency lines between tasks
	 * @method clearDependencies
	 * @return {Void}
	 */ this.clearDependencies = function()
	{
		var parent = document.getElementById('rightside');
		var depLine;
		var vMaxId = vDepId;
		for ( i=1; i<vMaxId; i++ ) {
			depLine = document.getElementById("line"+i);
			if (depLine) { parent.removeChild(depLine); }
		};
		vDepId = 1;
	};
	/**
	 * Draw a straight line (colored one-pixel wide DIV), need to parameterize doc item
	 * @method sLine
	 * @return {Void}
	 */  this.sLine = function(x1,y1,x2,y2) {

		vLeft = Math.min(x1,x2);
		vTop  = Math.min(y1,y2);
		vWid  = Math.abs(x2-x1) + 1;
		vHgt  = Math.abs(y2-y1) + 1;

		vDoc = document.getElementById('rightside');

		// retrieve DIV
		var oDiv = document.createElement('div');

		oDiv.id = "line"+vDepId++;
		oDiv.style.position = "absolute";
		oDiv.style.margin = "0px";
		oDiv.style.padding = "0px";
		oDiv.style.overflow = "hidden";
		oDiv.style.border = "0px";

		// set attributes
		oDiv.style.zIndex = 0;
		oDiv.style.backgroundColor = "red";

		oDiv.style.left = vLeft + "px";
		oDiv.style.top = vTop + "px";
		oDiv.style.width = vWid + "px";
		oDiv.style.height = vHgt + "px";

		oDiv.style.visibility = "visible";

		vDoc.appendChild(oDiv);

	};

	/**
	 * Draw a diaganol line (calc line x,y pairs and draw multiple one-by-one sLines)
	 * @method dLine
	 * @return {Void}
	 */  this.dLine = function(x1,y1,x2,y2) {

		var dx = x2 - x1;
		var dy = y2 - y1;
		var x = x1;
		var y = y1;

		var n = Math.max(Math.abs(dx),Math.abs(dy));
		dx = dx / n;
		dy = dy / n;
		for ( i = 0; i <= n; i++ )
		{
			vx = Math.round(x);
			vy = Math.round(y);
			this.sLine(vx,vy,vx,vy);
			x += dx;
			y += dy;
		};

	};

	/**
	 * Draw dependency line between two points (task 1 end -> task 2 start)
	 * @method drawDependency
	 * @return {Void}
	 */ this.drawDependency =function(x1,y1,x2,y2)
	{
		if(x1 + 10 < x2)
		{
			this.sLine(x1,y1,x1+4,y1);
			this.sLine(x1+4,y1,x1+4,y2);
			this.sLine(x1+4,y2,x2,y2);
			this.dLine(x2,y2,x2-3,y2-3);
			this.dLine(x2,y2,x2-3,y2+3);
			this.dLine(x2-1,y2,x2-3,y2-2);
			this.dLine(x2-1,y2,x2-3,y2+2);
		}
		else
		{
			this.sLine(x1,y1,x1+4,y1);
			this.sLine(x1+4,y1,x1+4,y2-10);
			this.sLine(x1+4,y2-10,x2-8,y2-10);
			this.sLine(x2-8,y2-10,x2-8,y2);
			this.sLine(x2-8,y2,x2,y2);
			this.dLine(x2,y2,x2-3,y2-3);
			this.dLine(x2,y2,x2-3,y2+3);
			this.dLine(x2-1,y2,x2-3,y2-2);
			this.dLine(x2-1,y2,x2-3,y2+2);
		}
	};

	/**
	 * Draw all task dependencies
	 * @method DrawDependencies
	 * @return {Void}
	 */  this.DrawDependencies = function () {

		//First recalculate the x,y
		this.CalcTaskXY();

		this.clearDependencies();

		var vList = this.getList();
		for(var i = 0; i < vList.length; i++)
		{

			vDepend = vList[i].getDepend();
			if(vDepend) {

				var vDependStr = vDepend + '';
				var vDepList = vDependStr.split(',');
				var n = vDepList.length;

				for(var k=0;k<n;k++) {
					var vTask = this.getArrayLocationByID(vDepList[k]);

					if(vList[vTask].getVisible()==1)
						this.drawDependency(vList[vTask].getEndX(),vList[vTask].getEndY(),vList[i].getStartX()-1,vList[i].getStartY())
				}
			}
		}
	};

	/**
	 * Find location of TaskItem based on the task ID
	 * @method getArrayLocationByID
	 * @return {Void}
	 */  this.getArrayLocationByID = function(pId)  {

		var vList = this.getList();
		for(var i = 0; i < vList.length; i++)
		{
			if(vList[i].getID()==pId)
				return i;
		}
	};

	/**
	 * Draw gantt chart
	 * @method Draw
	 * @return {Void}
	 */ this.Draw = function()
	{
		var vMaxDate = new Date();
		var vMinDate = new Date();
		var vTmpDate = new Date();
		var vNxtDate = new Date();
		var vCurrDate = new Date();
		var vTaskLeft = 0;
		var vTaskRight = 0;
		var vNumCols = 0;
		var vID = 0;
		var vMainTable = "";
		var vLeftTable = "";
		var vRightTable = "";
		var vDateRowStr = "";
		var vItemRowStr = "";
		var vColWidth = 0;
		var vColUnit = 0;
		var vChartWidth = 0;
		var vNumDays = 0;
		var vDayWidth = 0;
		var vStr = "";
		var vNameWidth = 220;
		var vStatusWidth = 70;
		var vLeftWidth = 15 + 220 + 70 + 70 + 70 + 70 + 70;

		if(vTaskList.length > 0)
		{

			// Process all tasks preset parent date and completion %
			JSGantt.processRows(vTaskList, 0, -1, 1, 1);

			// get overall min/max dates plus padding
			vMinDate = JSGantt.getMinDate(vTaskList, vFormat);
			vMaxDate = JSGantt.getMaxDate(vTaskList, vFormat);

			// Calculate chart width variables.  vColWidth can be altered manually to change each column width
			// May be smart to make this a parameter of GanttChart or set it based on existing pWidth parameter
			if(vFormat == 'day') {
				vColWidth = 18;
				vColUnit = 1;
			}
			else if(vFormat == 'week') {
				vColWidth = 37;
				vColUnit = 7;
			}
			else if(vFormat == 'month') {
				vColWidth = 37;
				vColUnit = 30;
			}
			else if(vFormat == 'quarter') {
				vColWidth = 60;
				vColUnit = 90;
			}

			else if(vFormat=='hour')
			{
				vColWidth = 18;
				vColUnit = 1;
			}

			else if(vFormat=='minute')
			{
				vColWidth = 18;
				vColUnit = 1;
			}

			vNumDays = (Date.parse(vMaxDate) - Date.parse(vMinDate)) / ( 24 * 60 * 60 * 1000);
			vNumUnits = vNumDays / vColUnit;


			vChartWidth = vNumUnits * vColWidth + 1;
			vDayWidth = (vColWidth / vColUnit) + (1/vColUnit);

			vMainTable =
					'<TABLE id=theTable cellSpacing=0 cellPadding=0 border=0><TBODY><TR>' +
					'<TD vAlign=top bgColor=#ffffff style="border-top:1px solid #efefef;border-bottom:1px solid #efefef">';

			if(vShowRes !=1) vNameWidth+=vStatusWidth;
			if(vShowDur !=1) vNameWidth+=vStatusWidth;
			if(vShowComp!=1) vNameWidth+=vStatusWidth;
			if(vShowStartDate!=1) vNameWidth+=vStatusWidth;
			if(vShowEndDate!=1) vNameWidth+=vStatusWidth;

			// DRAW the Left-side of the chart (names, resources, comp%)
			vLeftTable =
					'<DIV class=ganttscroll id=leftside style="width:' + vLeftWidth + 'px;border-top:0;border-bottom:0"><TABLE cellSpacing=0 cellPadding=0 border=0><TBODY>' +
					'<TR style="HEIGHT: 17px">' +
					'  <TD style="WIDTH: 15px; HEIGHT: 20px"></TD>' +
					'  <TD style="WIDTH: ' + vNameWidth + 'px; HEIGHT: 17px"><NOBR></NOBR></TD>';

			if(vShowRes ==1) vLeftTable += '  <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
			if(vShowDur ==1) vLeftTable += '  <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
			if(vShowComp==1) vLeftTable += '  <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
			if(vShowStartDate==1) vLeftTable += '  <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
			if(vShowEndDate==1) vLeftTable += '  <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;

			vLeftTable +=
					'<TR style="HEIGHT: 20px">' +
					'  <TD style="BORDER-TOP: #efefef 1px solid; WIDTH: 15px; HEIGHT: 20px"></TD>' +
					'  <TD style="BORDER-TOP: #efefef 1px solid; WIDTH: ' + vNameWidth + 'px; HEIGHT: 20px"><NOBR></NOBR></TD>' ;

			if(vShowRes ==1) vLeftTable += '  <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>指派给</TD>' ;
			if(vShowDur ==1) vLeftTable += '  <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>时长</TD>' ;
			if(vShowComp==1) vLeftTable += '  <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>进度%</TD>' ;
			if(vShowStartDate==1) vLeftTable += '  <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>开始时间</TD>' ;
			if(vShowEndDate==1) vLeftTable += '  <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>截止时间</TD>' ;

			vLeftTable += '</TR>';

			for(i = 0; i < vTaskList.length; i++)
			{
				if( vTaskList[i].getGroup()) {
					vBGColor = "f3f3f3";
					vRowType = "group";
				} else {
					vBGColor  = "ffffff";
					vRowType  = "row";
				}

				vID = vTaskList[i].getID();

				if(vTaskList[i].getVisible() == 0)
					vLeftTable += '<TR id=child_' + vID + ' bgcolor=#' + vBGColor + ' style="display:none"  onMouseover=gantg.mouseOver(this,' + vID + ',"left","' + vRowType + '") onMouseout=gantg.mouseOut(this,' + vID + ',"left","' + vRowType + '")>' ;
				else
					vLeftTable += '<TR id=child_' + vID + ' bgcolor=#' + vBGColor + ' onMouseover=gantg.mouseOver(this,' + vID + ',"left","' + vRowType + '") onMouseout=gantg.mouseOut(this,' + vID + ',"left","' + vRowType + '")>' ;

				vLeftTable +=
						'  <TD class=gdatehead style="WIDTH: 15px; HEIGHT: 20px; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: none">&nbsp;</TD>' +
						'  <TD class=gname style="WIDTH: ' + vNameWidth + 'px; HEIGHT: 20px; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px;" nowrap><NOBR><span style="color: #aaaaaa">';

				for(j=1; j<vTaskList[i].getLevel(); j++) {
					vLeftTable += '&nbsp&nbsp&nbsp&nbsp';
				}

				vLeftTable += '</span>';

				if( vTaskList[i].getGroup()) {
					if( vTaskList[i].getOpen() == 1)
						vLeftTable += '<SPAN id="group_' + vID + '" style="color:#000000; cursor:pointer; font-weight:bold; FONT-SIZE: 12px;" onclick="JSGantt.folder(' + vID + ','+vGanttVar+');'+vGanttVar+'.DrawDependencies();">&ndash;</span><span style="color:#000000">&nbsp</SPAN>' ;
					else
						vLeftTable += '<SPAN id="group_' + vID + '" style="color:#000000; cursor:pointer; font-weight:bold; FONT-SIZE: 12px;" onclick="JSGantt.folder(' + vID + ','+vGanttVar+');'+vGanttVar+'.DrawDependencies();">+</span><span style="color:#000000">&nbsp</SPAN>' ;

				} else {

					vLeftTable += '<span style="color: #000000; font-weight:bold; FONT-SIZE: 12px;">&nbsp&nbsp&nbsp</span>';
				}

				vLeftTable +=
						'<span onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200); style="cursor:pointer"> ' + vTaskList[i].getName() + '</span></NOBR></TD>' ;

				if(vShowRes ==1) vLeftTable += '  <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + vTaskList[i].getResource() + '</NOBR></TD>' ;
				if(vShowDur ==1) vLeftTable += '  <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + vTaskList[i].getDuration(vFormat) + '</NOBR></TD>' ;
				if(vShowComp==1) vLeftTable += '  <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + vTaskList[i].getCompStr()  + '</NOBR></TD>' ;
				if(vShowStartDate==1) vLeftTable += '  <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + JSGantt.formatDateStr( vTaskList[i].getStart(), vDateDisplayFormat) + '</NOBR></TD>' ;
				if(vShowEndDate==1) vLeftTable += '  <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + JSGantt.formatDateStr( vTaskList[i].getEnd(), vDateDisplayFormat) + '</NOBR></TD>' ;

				vLeftTable += '</TR>';

			}

			// DRAW the date format selector at bottom left.  Another potential GanttChart parameter to hide/show this selector
			vLeftTable += '</TD></TR>' +
					'<TR><TD border=1 colspan=15 align=left style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 11px; BORDER-LEFT:none; height:40px">&nbsp;&nbsp;格式:';

			if (vFormatArr.join().indexOf("minute")!=-1) {
				if (vFormat=='minute') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="minute" checked>分';
				else                vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("minute",'+vGanttVar+'); VALUE="minute">分';
			}

			if (vFormatArr.join().indexOf("hour")!=-1) {
				if (vFormat=='hour') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="hour" checked>小时';
				else                vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("hour",'+vGanttVar+'); VALUE="hour">小时';
			}

			if (vFormatArr.join().indexOf("day")!=-1) {
				if (vFormat=='day') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="day" checked>天';
				else                vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("day",'+vGanttVar+'); VALUE="day">天';
			}

			if (vFormatArr.join().indexOf("week")!=-1) {
				if (vFormat=='week') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="week" checked>周';
				else                vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("week",'+vGanttVar+') VALUE="week">周';
			}

			if (vFormatArr.join().indexOf("month")!=-1) {
				if (vFormat=='month') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="month" checked>月';
				else                vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("month",'+vGanttVar+') VALUE="month">月';
			}

			if (vFormatArr.join().indexOf("quarter")!=-1) {
				if (vFormat=='quarter') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="quarter" checked>季';
				else                vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("quarter",'+vGanttVar+') VALUE="quarter">季';
			}

//            vLeftTable += '<INPUT TYPE=RADIO NAME="other" VALUE="other" style="display:none"> .';

			vLeftTable += '</TD></TR></TBODY></TABLE></TD>';

			vMainTable += vLeftTable;

			// Draw the Chart Rows
			//console.log($(vDiv).width());
			vRightTable =
					'<TD style="width: ' + vChartWidth + 'px;border:1px solid #efefef" vAlign=top bgColor=#ffffff>' +
					'<DIV class=ganttscroll2 id=rightside style="width:'+($(vDiv).width()-vLeftWidth)+'px">' +
					'<TABLE style="width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
					'<TBODY><TR style="HEIGHT: 18px">';

			vTmpDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
			vTmpDate.setHours(0);
			vTmpDate.setMinutes(0);

			// Major Date Header
			while(Date.parse(vTmpDate) <= Date.parse(vMaxDate))
			{
				vStr = vTmpDate.getFullYear() + '';
				vStr = vStr.substring(2,4);


				if(vFormat == 'minute')
				{
					vRightTable += '<td class=gdatehead style="FONT-SIZE: 12px; HEIGHT: 19px;" align=center colspan=60>' ;
					vRightTable += JSGantt.formatDateStr(vTmpDate, vDateDisplayFormat) + ' ' + vTmpDate.getHours() + ':00 -' + vTmpDate.getHours() + ':59 </td>';
					vTmpDate.setHours(vTmpDate.getHours()+1);
				}

				if(vFormat == 'hour')
				{
					vRightTable += '<td class=gdatehead style="FONT-SIZE: 12px; HEIGHT: 19px;" align=center colspan=24>' ;
					vRightTable += JSGantt.formatDateStr(vTmpDate, vDateDisplayFormat) + '</td>';
					vTmpDate.setDate(vTmpDate.getDate()+1);
				}

				if(vFormat == 'day')
				{
					vRightTable += '<td class=gdatehead style="FONT-SIZE: 12px; HEIGHT: 19px;" align=center colspan=7>' +
							JSGantt.formatDateStr(vTmpDate,"yyyy-mm-dd") + ' 至 ';
					vTmpDate.setDate(vTmpDate.getDate()+6);
					vRightTable += JSGantt.formatDateStr(vTmpDate, "yyyy-mm-dd").substring(5) + '</td>';
					vTmpDate.setDate(vTmpDate.getDate()+1);
				}
				else if(vFormat == 'week')
				{
					vRightTable += '<td class=gdatehead align=center style="FONT-SIZE: 12px; HEIGHT: 19px;" width='+vColWidth+'px>`'+ vStr + '</td>';
					vTmpDate.setDate(vTmpDate.getDate()+7);
				}
				else if(vFormat == 'month')
				{
					vRightTable += '<td class=gdatehead align=center style="FONT-SIZE: 12px; HEIGHT: 19px;" width='+vColWidth+'px>`'+ vStr + '</td>';
					vTmpDate.setDate(vTmpDate.getDate() + 1);
					while(vTmpDate.getDate() > 1)
					{
						vTmpDate.setDate(vTmpDate.getDate() + 1);
					}
				}
				else if(vFormat == 'quarter')
				{
					vRightTable += '<td class=gdatehead align=center style="FONT-SIZE: 12px; HEIGHT: 19px;" width='+vColWidth+'px>`'+ vStr + '</td>';
					vTmpDate.setDate(vTmpDate.getDate() + 81);
					while(vTmpDate.getDate() > 1)
					{
						vTmpDate.setDate(vTmpDate.getDate() + 1);
					}
				}

			}

			vRightTable += '</TR><TR>';

			// Minor Date header and Cell Rows
			vTmpDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
			vNxtDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
			vNumCols = 0;

			while(Date.parse(vTmpDate) <= Date.parse(vMaxDate))
			{
				if (vFormat == 'minute')
				{

					if( vTmpDate.getMinutes() ==0 )
						vWeekdayColor = "ccccff";
					else
						vWeekdayColor = "ffffff";


					vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;"  bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getMinutes() + '</div></td>';
					vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;"  bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
					vTmpDate.setMinutes(vTmpDate.getMinutes() + 1);
				}

				else if (vFormat == 'hour')
				{

					if(  vTmpDate.getHours() ==0  )
						vWeekdayColor = "ccccff";
					else
						vWeekdayColor = "ffffff";


					vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;"  bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getHours() + '</div></td>';
					vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;"  bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
					vTmpDate.setHours(vTmpDate.getHours() + 1);
				}

				else if(vFormat == 'day' )
				{
					if( JSGantt.formatDateStr(vCurrDate,'mm/dd/yyyy') == JSGantt.formatDateStr(vTmpDate,'mm/dd/yyyy')) {
						vWeekdayColor  = "ccccff";
						vWeekendColor  = "9999ff";
						vWeekdayGColor  = "bbbbff";
						vWeekendGColor = "8888ff";
					} else {
						vWeekdayColor = "ffffff";
						vWeekendColor = "cfcfcf";
						vWeekdayGColor = "f3f3f3";
						vWeekendGColor = "c3c3c3";
					}

					if(vTmpDate.getDay() % 6 == 0) {
						vDateRowStr  += '<td class="gheadwkend" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekendColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getDate() + '</div></td>';
						vItemRowStr  += '<td class="gheadwkend" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;"  bgcolor=#' + vWeekendColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp</div></td>';
					}
					else {
						vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;"  bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getDate() + '</div></td>';
						if( JSGantt.formatDateStr(vCurrDate,'mm/dd/yyyy') == JSGantt.formatDateStr(vTmpDate,'mm/dd/yyyy'))
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;"  bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
						else
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;"  align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
					}

					vTmpDate.setDate(vTmpDate.getDate() + 1);

				}

				else if(vFormat == 'week')
				{

					vNxtDate.setDate(vNxtDate.getDate() + 7);

					if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
						vWeekdayColor = "ccccff";
					else
						vWeekdayColor = "ffffff";

					if(vNxtDate <= vMaxDate) {
						vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + (vTmpDate.getMonth()+1) + '/' + vTmpDate.getDate() + '</div></td>';
						if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
						else
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';

					} else {
						vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid; bgcolor=#' + vWeekdayColor + ' BORDER-RIGHT: #efefef 1px solid;" align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + (vTmpDate.getMonth()+1) + '/' + vTmpDate.getDate() + '</div></td>';
						if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
						else
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';

					}

					vTmpDate.setDate(vTmpDate.getDate() + 7);

				}

				else if(vFormat == 'month')
				{

					vNxtDate.setFullYear(vTmpDate.getFullYear(), vTmpDate.getMonth(), vMonthDaysArr[vTmpDate.getMonth()]);
					if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
						vWeekdayColor = "ccccff";
					else
						vWeekdayColor = "ffffff";

					if(vNxtDate <= vMaxDate) {
						vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + vMonthArr[vTmpDate.getMonth()].substr(0,3) + '</div></td>';
						if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
						else
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
					} else {
						vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + vMonthArr[vTmpDate.getMonth()].substr(0,3) + '</div></td>';
						if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
						else
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
					}

					vTmpDate.setDate(vTmpDate.getDate() + 1);

					while(vTmpDate.getDate() > 1)
					{
						vTmpDate.setDate(vTmpDate.getDate() + 1);
					}

				}

				else if(vFormat == 'quarter')
				{

					vNxtDate.setDate(vNxtDate.getDate() + 122);
					if( vTmpDate.getMonth()==0 || vTmpDate.getMonth()==1 || vTmpDate.getMonth()==2 )
						vNxtDate.setFullYear(vTmpDate.getFullYear(), 2, 31);
					else if( vTmpDate.getMonth()==3 || vTmpDate.getMonth()==4 || vTmpDate.getMonth()==5 )
						vNxtDate.setFullYear(vTmpDate.getFullYear(), 5, 30);
					else if( vTmpDate.getMonth()==6 || vTmpDate.getMonth()==7 || vTmpDate.getMonth()==8 )
						vNxtDate.setFullYear(vTmpDate.getFullYear(), 8, 30);
					else if( vTmpDate.getMonth()==9 || vTmpDate.getMonth()==10 || vTmpDate.getMonth()==11 )
						vNxtDate.setFullYear(vTmpDate.getFullYear(), 11, 31);

					if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
						vWeekdayColor = "ccccff";
					else
						vWeekdayColor = "ffffff";

					if(vNxtDate <= vMaxDate) {
						vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">第' + vQuarterArr[vTmpDate.getMonth()] + '季</div></td>';
						if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
						else
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
					} else {
						vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">第' + vQuarterArr[vTmpDate.getMonth()] + '季</div></td>';
						if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
						else
							vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">&nbsp&nbsp</div></td>';
					}

					vTmpDate.setDate(vTmpDate.getDate() + 81);

					while(vTmpDate.getDate() > 1)
					{
						vTmpDate.setDate(vTmpDate.getDate() + 1);
					}

				}
			}

			vRightTable += vDateRowStr + '</TR>';
			vRightTable += '</TBODY></TABLE>';

			// Draw each row

			for(i = 0; i < vTaskList.length; i++)

			{

				vTmpDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
				vTaskStart = vTaskList[i].getStart();
				vTaskEnd   = vTaskList[i].getEnd();

				vNumCols = 0;
				vID = vTaskList[i].getID();

				// vNumUnits = Math.ceil((vTaskList[i].getEnd() - vTaskList[i].getStart()) / (24 * 60 * 60 * 1000)) + 1;
				vNumUnits = (vTaskList[i].getEnd() - vTaskList[i].getStart()) / (24 * 60 * 60 * 1000) + 1;
				if (vFormat=='hour')
				{
					vNumUnits = (vTaskList[i].getEnd() - vTaskList[i].getStart()) / (  60 * 1000) + 1;
				}
				else if (vFormat=='minute')
				{
					vNumUnits = (vTaskList[i].getEnd() - vTaskList[i].getStart()) / (  60 * 1000) + 1;
				}

				if(vTaskList[i].getVisible() == 0)
					vRightTable += '<DIV id=childgrid_' + vID + ' style="position:relative; display:none;">';
				else
					vRightTable += '<DIV id=childgrid_' + vID + ' style="position:relative">';

				if( vTaskList[i].getMile()) {

					vRightTable += '<DIV><TABLE style="position:relative; top:0px; width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
							'<TR id=childrow_' + vID + ' class=yesdisplay style="HEIGHT: 20px" onMouseover=gantg.mouseOver(this,' + vID + ',"right","mile") onMouseout=gantg.mouseOut(this,' + vID + ',"right","mile")>' + vItemRowStr + '</TR></TABLE></DIV>';

					// Build date string for Title
					vDateRowStr = JSGantt.formatDateStr(vTaskStart,vDateDisplayFormat);

					vTaskLeft = (Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / (24 * 60 * 60 * 1000);
					vTaskRight = 1;

					vRightTable +=
							'<div id=bardiv_' + vID + ' style="position:absolute; top:0px; left:' + Math.ceil((vTaskLeft * (vDayWidth) + 1)) + 'px; height: 18px; width:160px; overflow:hidden;">' +
							'  <div id=taskbar_' + vID + ' title="' + vTaskList[i].getName() + ': ' + vDateRowStr + '" style="height: 16px; width:12px; overflow:hidden; cursor: pointer;" onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200);>';

					if(vTaskList[i].getCompVal() < 100)
					{vRightTable += '&loz;</div>' ;}
					else
					{ vRightTable += '&diams;</div>' ;}

					if( gantg.getCaptionType() ) {
						vCaptionStr = '';
						switch( gantg.getCaptionType() ) {
							case 'Caption':    vCaptionStr = vTaskList[i].getCaption();  break;
							case 'Resource':   vCaptionStr = vTaskList[i].getResource();  break;
							case 'Duration':   vCaptionStr = vTaskList[i].getDuration(vFormat);  break;
							case 'Complete':   vCaptionStr = vTaskList[i].getCompStr();  break;
						}
						//vRightTable += '<div style="FONT-SIZE:12px; position:absolute; left: 6px; top:1px;">' + vCaptionStr + '</div>';
						vRightTable += '<div style="FONT-SIZE:12px; position:absolute; top:2px; width:120px; left:12px">' + vCaptionStr + '</div>';
					};

					vRightTable += '</div>';


				} else {

					// Build date string for Title
					vDateRowStr = JSGantt.formatDateStr(vTaskStart,vDateDisplayFormat) + ' - ' + JSGantt.formatDateStr(vTaskEnd,vDateDisplayFormat);

					if (vFormat=='minute')
					{
						vTaskRight = (Date.parse(vTaskList[i].getEnd()) - Date.parse(vTaskList[i].getStart())) / ( 60 * 1000) + 1/vColUnit;
						vTaskLeft = Math.ceil((Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / ( 60 * 1000));
					}
					else if (vFormat=='hour')
					{
						vTaskRight = (Date.parse(vTaskList[i].getEnd()) - Date.parse(vTaskList[i].getStart())) / ( 60 * 60 * 1000) + 1/vColUnit;
						vTaskLeft = (Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / ( 60 * 60 * 1000);
					}
					else
					{
						vTaskRight = (Date.parse(vTaskList[i].getEnd()) - Date.parse(vTaskList[i].getStart())) / (24 * 60 * 60 * 1000) + 1/vColUnit;
						vTaskLeft = Math.ceil((Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / (24 * 60 * 60 * 1000));
						if (vFormat='day')
						{
							var tTime=new Date();
							tTime.setTime(Date.parse(vTaskList[i].getStart()));
							if (tTime.getMinutes() > 29)
								vTaskLeft+=.5;
						}
					}

					// Draw Group Bar  which has outer div with inner group div and several small divs to left and right to create angled-end indicators
					if( vTaskList[i].getGroup()) {
						vRightTable += '<DIV><TABLE style="position:relative; top:0px; width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
								'<TR id=childrow_' + vID + ' class=yesdisplay style="HEIGHT: 20px" bgColor=#f3f3f3 onMouseover=gantg.mouseOver(this,' + vID + ',"right","group") onMouseout=gantg.mouseOut(this,' + vID + ',"right","group")>' + vItemRowStr + '</TR></TABLE></DIV>';
						vRightTable +=
								'<div id=bardiv_' + vID + ' style="position:absolute; top:5px; left:' + Math.ceil(vTaskLeft * (vDayWidth) + 1) + 'px; height: 7px; width:' + Math.ceil((vTaskRight) * (vDayWidth) - 1) + 'px">' +
								'<div id=taskbar_' + vID + ' title="' + vTaskList[i].getName() + ': ' + vDateRowStr + '" class=gtask style="background-color:#000000; height: 7px; width:' + Math.ceil((vTaskRight) * (vDayWidth) -1) + 'px;  cursor: pointer;opacity:0.9;">' +
								'<div style="Z-INDEX: -4; float:left; background-color:#666666; height:3px; overflow: hidden; margin-top:1px; ' +
								'margin-left:1px; margin-right:1px; filter: alpha(opacity=80); opacity:0.8; width:' + vTaskList[i].getCompStr() + '; ' +
								'cursor: pointer;" onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200);>' +
								'</div>' +
								'</div>' +
								'<div style="Z-INDEX: -4; float:left; background-color:#000000; height:4px; overflow: hidden; width:1px;"></div>' +
								'<div style="Z-INDEX: -4; float:right; background-color:#000000; height:4px; overflow: hidden; width:1px;"></div>' +
								'<div style="Z-INDEX: -4; float:left; background-color:#000000; height:3px; overflow: hidden; width:1px;"></div>' +
								'<div style="Z-INDEX: -4; float:right; background-color:#000000; height:3px; overflow: hidden; width:1px;"></div>' +
								'<div style="Z-INDEX: -4; float:left; background-color:#000000; height:2px; overflow: hidden; width:1px;"></div>' +
								'<div style="Z-INDEX: -4; float:right; background-color:#000000; height:2px; overflow: hidden; width:1px;"></div>' +
								'<div style="Z-INDEX: -4; float:left; background-color:#000000; height:1px; overflow: hidden; width:1px;"></div>' +
								'<div style="Z-INDEX: -4; float:right; background-color:#000000; height:1px; overflow: hidden; width:1px;"></div>' ;

						if( gantg.getCaptionType() ) {
							vCaptionStr = '';
							switch( gantg.getCaptionType() ) {
								case 'Caption':    vCaptionStr = vTaskList[i].getCaption();  break;
								case 'Resource':   vCaptionStr = vTaskList[i].getResource();  break;
								case 'Duration':   vCaptionStr = vTaskList[i].getDuration(vFormat);  break;
								case 'Complete':   vCaptionStr = vTaskList[i].getCompStr();  break;
							}
							//vRightTable += '<div style="FONT-SIZE:12px; position:absolute; left: 6px; top:1px;">' + vCaptionStr + '</div>';
							vRightTable += '<div style="FONT-SIZE:12px; position:absolute; top:-3px; width:120px; left:' + (Math.ceil((vTaskRight) * (vDayWidth) - 1) + 6) + 'px">' + vCaptionStr + '</div>';
						};

						vRightTable += '</div>' ;

					} else {

						vDivStr = '<DIV><TABLE style="position:relative; top:0px; width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
								'<TR id=childrow_' + vID + ' class=yesdisplay style="HEIGHT: 20px" bgColor=#ffffff onMouseover=gantg.mouseOver(this,' + vID + ',"right","row") onMouseout=gantg.mouseOut(this,' + vID + ',"right","row")>' + vItemRowStr + '</TR></TABLE></DIV>';
						vRightTable += vDivStr;

						// Draw Task Bar  which has outer DIV with enclosed colored bar div, and opaque completion div
						vRightTable +=
								'<div id=bardiv_' + vID + ' style="position:absolute; top:4px; left:' + Math.ceil(vTaskLeft * (vDayWidth) + 1) + 'px; height:18px; width:' + Math.ceil((vTaskRight) * (vDayWidth) - 1) + 'px">' +
								'<div id=taskbar_' + vID + ' title="' + vTaskList[i].getName() + ': ' + vDateRowStr + '" class=gtask style="background-color:#' + vTaskList[i].getColor() +'; height: 13px; width:' + Math.ceil((vTaskRight) * (vDayWidth) - 1) + 'px; cursor: pointer;opacity:0.9;" ' +
								'onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200); >' +
								'<div class=gcomplete style="Z-INDEX: -4; float:left; background-color:black; height:5px; overflow: auto; margin-top:4px; filter: alpha(opacity=40); opacity:0.4; width:' + vTaskList[i].getCompStr() + '; overflow:hidden">' +
								'</div>' +
								'</div>';

						if( gantg.getCaptionType() ) {
							vCaptionStr = '';
							switch( gantg.getCaptionType() ) {
								case 'Caption':    vCaptionStr = vTaskList[i].getCaption();  break;
								case 'Resource':   vCaptionStr = vTaskList[i].getResource();  break;
								case 'Duration':   vCaptionStr = vTaskList[i].getDuration(vFormat);  break;
								case 'Complete':   vCaptionStr = vTaskList[i].getCompStr();  break;
							}
							//vRightTable += '<div style="FONT-SIZE:12px; position:absolute; left: 6px; top:-3px;">' + vCaptionStr + '</div>';
							vRightTable += '<div style="FONT-SIZE:12px; position:absolute; top:-3px; width:120px; left:' + (Math.ceil((vTaskRight) * (vDayWidth) - 1) + 6) + 'px">' + vCaptionStr + '</div>';
						}
						vRightTable += '</div>' ;



					}
				}

				vRightTable += '</DIV>';

			}
			vRightTable +='</DIV>';

			var sColorStr="";
			for(var i=0,l=vgantColor.length;i<l;i++){
				sColorStr+="P"+i+ '<span style="display:inline-block;padding:5px 10px;margin:0 10px 0 5px;background:#'+vgantColor[i]+'"> </span> ';
			}
			vRightTable +="<div style='text-align:right;padding-right:10px'>"+sColorStr+"</div>";
			vMainTable +=vRightTable+'</TD></TR></TBODY></TABLE>';

			vDiv.innerHTML = vMainTable;

		}

	}; //this.draw

	/**
	 * Mouseover behaviour for gantt row
	 * @method mouseOver
	 * @return {Void}
	 */  this.mouseOver = function( pObj, pID, pPos, pType ) {
		if( pPos == 'right' )  vID = 'child_' + pID;
		else vID = 'childrow_' + pID;
		$(pObj).css("background","#ffffaa");
		vRowObj = JSGantt.findObj(vID);
		if (vRowObj)
			$(vRowObj).css("background","#ffffaa");
		//if (vRowObj) vRowObj.bgColor = "#ffffaa";
	};

	/**
	 * Mouseout behaviour for gantt row
	 * @method mouseOut
	 * @return {Void}
	 */  this.mouseOut = function( pObj, pID, pPos, pType ) {
		if( pPos == 'right' )  vID = 'child_' + pID;
		else vID = 'childrow_' + pID;
		$(pObj).css("background","#ffffff");
		//pObj.bgColor = "#ffffff";
		vRowObj = JSGantt.findObj(vID);
		if (vRowObj) {
			if( pType == "group") {
				$(pObj).css("background","#f3f3f3");
				$(vRowObj).css("background","#f3f3f3");
				// pObj.bgColor = "#f3f3f3";
				// vRowObj.bgColor = "#f3f3f3";
			} else {
				$(pObj).css("background","#ffffff");
				//pObj.bgColor = "#ffffff";
				$(vRowObj).css("background","#ffffff");
				//vRowObj.bgColor = "#ffffff";
			}
		}
	};

}; //GanttChart


/**
 *
 @class
 */

/**
 * Checks whether browser is IE
 *
 * @method isIE
 */
JSGantt.isIE = function () {

	if(typeof document.all != 'undefined')
	{return true;}
	else
	{return false;}
};

/**
 * Recursively process task tree ... set min, max dates of parent tasks and identfy task level.
 *
 * @method processRows
 * @param pList {Array} - Array of TaskItem Objects
 * @param pID {Number} - task ID
 * @param pRow {Number} - Row in chart
 * @param pLevel {Number} - Current tree level
 * @param pOpen {Boolean}
 * @return void
 */
JSGantt.processRows = function(pList, pID, pRow, pLevel, pOpen)
{

	var vMinDate = new Date();
	var vMaxDate = new Date();
	var vMinSet  = 0;
	var vMaxSet  = 0;
	var vList    = pList;
	var vLevel   = pLevel;
	var i        = 0;
	var vNumKid  = 0;
	var vCompSum = 0;
	var vVisible = pOpen;

	for(i = 0; i < pList.length; i++)
	{
		if(pList[i].getParent() == pID) {
			vVisible = pOpen;
			pList[i].setVisible(vVisible);
			if(vVisible==1 && pList[i].getOpen() == 0)
			{vVisible = 0;}

			pList[i].setLevel(vLevel);
			vNumKid++;

			if(pList[i].getGroup() == 1) {
				JSGantt.processRows(vList, pList[i].getID(), i, vLevel+1, vVisible);
			};

			if( vMinSet==0 || pList[i].getStart() < vMinDate) {
				vMinDate = pList[i].getStart();
				vMinSet = 1;
			};

			if( vMaxSet==0 || pList[i].getEnd() > vMaxDate) {
				vMaxDate = pList[i].getEnd();
				vMaxSet = 1;
			};

			vCompSum += pList[i].getCompVal();

		}
	}

	if(pRow >= 0) {
		pList[pRow].setStart(vMinDate);
		pList[pRow].setEnd(vMaxDate);
		pList[pRow].setNumKid(vNumKid);
		pList[pRow].setCompVal(Math.ceil(vCompSum/vNumKid));
	}

};

/**
 * Determine the minimum date of all tasks and set lower bound based on format
 *
 * @method getMinDate
 * @param pList {Array} - Array of TaskItem Objects
 * @param pFormat {String} - current format (minute,hour,day...)
 * @return {Datetime}
 */
JSGantt.getMinDate = function getMinDate(pList, pFormat)
{

	var vDate = new Date();

	vDate.setFullYear(pList[0].getStart().getFullYear(), pList[0].getStart().getMonth(), pList[0].getStart().getDate());

	// Parse all Task End dates to find min
	for(i = 0; i < pList.length; i++)
	{
		if(Date.parse(pList[i].getStart()) < Date.parse(vDate))
			vDate.setFullYear(pList[i].getStart().getFullYear(), pList[i].getStart().getMonth(), pList[i].getStart().getDate());
	}

	if ( pFormat== 'minute')
	{
		vDate.setHours(0);
		vDate.setMinutes(0);
	}
	else if (pFormat == 'hour' )
	{
		vDate.setHours(0);
		vDate.setMinutes(0);
	}
	// Adjust min date to specific format boundaries (first of week or first of month)
	else if (pFormat=='day')
	{
		vDate.setDate(vDate.getDate() - 1);
		while(vDate.getDay() % 7 > 0)
		{
			vDate.setDate(vDate.getDate() - 1);
		}

	}

	else if (pFormat=='week')
	{
		vDate.setDate(vDate.getDate() - 7);
		while(vDate.getDay() % 7 > 0)
		{
			vDate.setDate(vDate.getDate() - 1);
		}

	}

	else if (pFormat=='month')
	{
		while(vDate.getDate() > 1)
		{
			vDate.setDate(vDate.getDate() - 1);
		}
	}

	else if (pFormat=='quarter')
	{
		if( vDate.getMonth()==0 || vDate.getMonth()==1 || vDate.getMonth()==2 )
		{vDate.setFullYear(vDate.getFullYear(), 0, 1);}
		else if( vDate.getMonth()==3 || vDate.getMonth()==4 || vDate.getMonth()==5 )
		{vDate.setFullYear(vDate.getFullYear(), 3, 1);}
		else if( vDate.getMonth()==6 || vDate.getMonth()==7 || vDate.getMonth()==8 )
		{vDate.setFullYear(vDate.getFullYear(), 6, 1);}
		else if( vDate.getMonth()==9 || vDate.getMonth()==10 || vDate.getMonth()==11 )
		{vDate.setFullYear(vDate.getFullYear(), 9, 1);}

	};

	return(vDate);

};




/**
 * Used to determine the minimum date of all tasks and set lower bound based on format
 *
 * @method getMaxDate
 * @param pList {Array} - Array of TaskItem Objects
 * @param pFormat {String} - current format (minute,hour,day...)
 * @return {Datetime}
 */
JSGantt.getMaxDate = function (pList, pFormat)
{
	var vDate = new Date();

	vDate.setFullYear(pList[0].getEnd().getFullYear(), pList[0].getEnd().getMonth(), pList[0].getEnd().getDate());


	// Parse all Task End dates to find max
	for(i = 0; i < pList.length; i++)
	{
		if(Date.parse(pList[i].getEnd()) > Date.parse(vDate))
		{
			//vDate.setFullYear(pList[0].getEnd().getFullYear(), pList[0].getEnd().getMonth(), pList[0].getEnd().getDate());
			vDate.setTime(Date.parse(pList[i].getEnd()));
		}
	}

	if (pFormat == 'minute')
	{
		vDate.setHours(vDate.getHours() + 1);
		vDate.setMinutes(59);
	}

	if (pFormat == 'hour')
	{
		vDate.setHours(vDate.getHours() + 2);
	}

	// Adjust max date to specific format boundaries (end of week or end of month)
	if (pFormat=='day')
	{
		vDate.setDate(vDate.getDate() + 1);

		while(vDate.getDay() % 6 > 0)
		{
			vDate.setDate(vDate.getDate() + 1);
		}

	}

	if (pFormat=='week')
	{
		//For weeks, what is the last logical boundary?
		vDate.setDate(vDate.getDate() + 11);

		while(vDate.getDay() % 6 > 0)
		{
			vDate.setDate(vDate.getDate() + 1);
		}

	}

	// Set to last day of current Month
	if (pFormat=='month')
	{
		while(vDate.getDay() > 1)
		{
			vDate.setDate(vDate.getDate() + 1);
		}

		vDate.setDate(vDate.getDate() - 1);
	}

	// Set to last day of current Quarter
	if (pFormat=='quarter')
	{
		if( vDate.getMonth()==0 || vDate.getMonth()==1 || vDate.getMonth()==2 )
			vDate.setFullYear(vDate.getFullYear(), 2, 31);
		else if( vDate.getMonth()==3 || vDate.getMonth()==4 || vDate.getMonth()==5 )
			vDate.setFullYear(vDate.getFullYear(), 5, 30);
		else if( vDate.getMonth()==6 || vDate.getMonth()==7 || vDate.getMonth()==8 )
			vDate.setFullYear(vDate.getFullYear(), 8, 30);
		else if( vDate.getMonth()==9 || vDate.getMonth()==10 || vDate.getMonth()==11 )
			vDate.setFullYear(vDate.getFullYear(), 11, 31);

	}

	return(vDate);

};


/**
 * Returns an object from the current DOM
 *
 * @method findObj
 * @param theObj {String} - Object name
 * @param theDoc {Document} - current document (DOM)
 * @return {Object}
 */
JSGantt.findObj = function (theObj, theDoc)

{

	var p, i, foundObj;

	if(!theDoc) {theDoc = document;}

	if( (p = theObj.indexOf("?")) > 0 && parent.frames.length){

		theDoc = parent.frames[theObj.substring(p+1)].document;

		theObj = theObj.substring(0,p);

	}

	if(!(foundObj = theDoc[theObj]) && theDoc.all)

	{foundObj = theDoc.all[theObj];}



	for (i=0; !foundObj && i < theDoc.forms.length; i++)

	{foundObj = theDoc.forms[i][theObj];}



	for(i=0; !foundObj && theDoc.layers && i < theDoc.layers.length; i++)

	{foundObj = JSGantt.findObj(theObj,theDoc.layers[i].document);}



	if(!foundObj && document.getElementById)

	{foundObj = document.getElementById(theObj);}



	return foundObj;

};


/**
 * Change display format of current gantt chart
 *
 * @method changeFormat
 * @param pFormat {String} - Current format (minute,hour,day...)
 * @param ganttObj {GanttChart} - The gantt object
 * @return {void}
 */
JSGantt.changeFormat =      function(pFormat,ganttObj) {

	if(ganttObj)
	{
		ganttObj.setFormat(pFormat);
		ganttObj.DrawDependencies();
	}
	else
	{alert('Chart undefined');};
};


/**
 * Open/Close and hide/show children of specified task
 *
 * @method folder
 * @param pID {Number} - Task ID
 * @param ganttObj {GanttChart} - The gantt object
 * @return {void}
 */
JSGantt.folder= function (pID,ganttObj) {

	var vList = ganttObj.getList();

	for(i = 0; i < vList.length; i++)
	{
		if(vList[i].getID() == pID) {

			if( vList[i].getOpen() == 1 ) {
				vList[i].setOpen(0);
				JSGantt.hide(pID,ganttObj);

				if (JSGantt.isIE())
				{JSGantt.findObj('group_'+pID).innerText = '+';}
				else
				{JSGantt.findObj('group_'+pID).textContent = '+';}

			} else {

				vList[i].setOpen(1);

				JSGantt.show(pID, 1, ganttObj);

				if (JSGantt.isIE())
				{JSGantt.findObj('group_'+pID).innerText = '－'}
				else
				{JSGantt.findObj('group_'+pID).textContent = '－'}

			}

		}
	}
};

/**
 * Hide children of a task
 *
 * @method hide
 * @param pID {Number} - Task ID
 * @param ganttObj {GanttChart} - The gantt object
 * @return {void}
 */
JSGantt.hide=     function (pID,ganttObj) {
	var vList = ganttObj.getList();
	var vID   = 0;

	for(var i = 0; i < vList.length; i++)
	{
		if(vList[i].getParent() == pID) {
			vID = vList[i].getID();
			JSGantt.findObj('child_' + vID).style.display = "none";
			JSGantt.findObj('childgrid_' + vID).style.display = "none";
			vList[i].setVisible(0);
			if(vList[i].getGroup() == 1)
			{JSGantt.hide(vID,ganttObj);}
		}

	}
};

/**
 * Show children of a task
 *
 * @method show
 * @param pID {Number} - Task ID
 * @param ganttObj {GanttChart} - The gantt object
 * @return {void}
 */
JSGantt.show =  function (pID, pTop, ganttObj) {
	var vList = ganttObj.getList();
	var vID   = 0;

	for(var i = 0; i < vList.length; i++)
	{
		if(vList[i].getParent() == pID) {
			vID = vList[i].getID();
			if(pTop == 1) {
				if (JSGantt.isIE()) { // IE;

					if( JSGantt.findObj('group_'+pID).innerText == '+') {
						JSGantt.findObj('child_'+vID).style.display = "";
						JSGantt.findObj('childgrid_'+vID).style.display = "";
						vList[i].setVisible(1);
					}

				} else {

					if( JSGantt.findObj('group_'+pID).textContent == '+') {
						JSGantt.findObj('child_'+vID).style.display = "";
						JSGantt.findObj('childgrid_'+vID).style.display = "";
						vList[i].setVisible(1);
					}

				}

			} else {

				if (JSGantt.isIE()) { // IE;
					if( JSGantt.findObj('group_'+pID).innerText == '－') {
						JSGantt.findObj('child_'+vID).style.display = "";
						JSGantt.findObj('childgrid_'+vID).style.display = "";
						vList[i].setVisible(1);
					}

				} else {

					if( JSGantt.findObj('group_'+pID).textContent == '－') {
						JSGantt.findObj('child_'+vID).style.display = "";
						JSGantt.findObj('childgrid_'+vID).style.display = "";
						vList[i].setVisible(1);
					}
				}
			}

			if(vList[i].getGroup() == 1)
			{JSGantt.show(vID, 0,ganttObj);}

		}
	}
};
/**
 * Handles click events on task name, currently opens a new window
 *
 * @method taskLink
 * @param pRef {String} - URL for window
 * @param pWidth {Number} - Width of window
 * @param pHeight {Number} - Height of window
 * @return {void}
 */
JSGantt.taskLink = function(pRef,pWidth,pHeight)

{

	if(pWidth)  {vWidth =pWidth;}  else {vWidth =400;}
	if(pHeight) {vHeight=pHeight;} else {vHeight=400;}

	//var OpenWindow=window.open(pRef, "newwin", "height="+vHeight+",width="+vWidth);
	// $target.modal(option).one()
	$("#ganttModalLink").attr("href",pRef);
	$("#ganttModalLink").trigger("click");
	//console.log(pRef,pWidth,pHeight)
};

/**
 * Parse dates based on gantt date format setting as defined in JSGantt.GanttChart.setDateInputFormat()
 *
 * @method parseDateStr
 * @param pDateStr {String} - A string that contains the date (i.e. "01/01/09")
 * @param pFormatStr {String} - The date format (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
 * @return {Datetime}
 */
JSGantt.parseDateStr = function(pDateStr,pFormatStr) {
	var vDate =new Date();
	vDate.setTime( Date.parse(pDateStr));

	switch(pFormatStr)
	{
		case 'mm/dd/yyyy':
			var vDateParts = pDateStr.split('/');
			vDate.setFullYear(parseInt(vDateParts[2], 10), parseInt(vDateParts[0], 10) - 1, parseInt(vDateParts[1], 10));
			break;
		case 'dd/mm/yyyy':
			var vDateParts = pDateStr.split('/');
			vDate.setFullYear(parseInt(vDateParts[2], 10), parseInt(vDateParts[1], 10) - 1, parseInt(vDateParts[0], 10));
			break;
		case 'yyyy-mm-dd':
			var vDateParts = pDateStr.split('-');
			vDate.setFullYear(parseInt(vDateParts[0], 10), parseInt(vDateParts[1], 10) - 1, parseInt(vDateParts[1], 10));
			break;
	}

	return(vDate);

};

/**
 * Display a formatted date based on gantt date format setting as defined in JSGantt.GanttChart.setDateDisplayFormat()
 *
 * @method formatDateStr
 * @param pDate {Date} - A javascript date object
 * @param pFormatStr {String} - The date format (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd...)
 * @return {String}
 */
JSGantt.formatDateStr = function(pDate,pFormatStr) {
	vYear4Str = pDate.getFullYear() + '';
	vYear2Str = vYear4Str.substring(2,4);
	vMonthStr = (pDate.getMonth()+1) + '';
	vDayStr   = pDate.getDate() + '';

	var vDateStr = "";

	switch(pFormatStr) {
		case 'mm/dd/yyyy':
			return( vMonthStr + '/' + vDayStr + '/' + vYear4Str );
		case 'dd/mm/yyyy':
			return( vDayStr + '/' + vMonthStr + '/' + vYear4Str );
		case 'yyyy-mm-dd':
			return( vYear4Str + '-' + vMonthStr + '-' + vDayStr );
		case 'mm/dd/yy':
			return( vMonthStr + '/' + vDayStr + '/' + vYear2Str );
		case 'dd/mm/yy':
			return( vDayStr + '/' + vMonthStr + '/' + vYear2Str );
		case 'yy-mm-dd':
			return( vYear2Str + '-' + vMonthStr + '-' + vDayStr );
		case 'mm/dd':
			return( vMonthStr + '/' + vDayStr );
		case 'dd/mm':
			return( vDayStr + '/' + vMonthStr );
	}

};

/**
 * Parse an external XML file containing task items.
 *
 * @method parseXML
 * @param ThisFile {String} - URL to XML file
 * @param pGanttVar {Gantt} - Gantt object
 * @return {void}
 */
JSGantt.parseXML = function(ThisFile,pGanttVar){
	var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;   // Is this Chrome

	try { //Internet Explorer
		xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
	}
	catch(e) {
		try { //Firefox, Mozilla, Opera, Chrome etc.
			if (is_chrome==false) {  xmlDoc=document.implementation.createDocument("","",null); }
		}
		catch(e) {
			alert(e.message);
			return;
		}
	}

	if (is_chrome==false) { 	// can't use xmlDoc.load in chrome at the moment
		xmlDoc.async=false;
		xmlDoc.load(ThisFile);		// we can use  loadxml
		JSGantt.AddXMLTask(pGanttVar);
		xmlDoc=null;			// a little tidying
		Task = null;
	}
	else {
		JSGantt.ChromeLoadXML(ThisFile,pGanttVar);
		ta=null;	// a little tidying
	}
};

/**
 * Add a task based on parsed XML doc
 *
 * @method AddXMLTask
 * @param pGanttVar {Gantt} - Gantt object
 * @return {void}
 */
JSGantt.AddXMLTask = function(pGanttVar){

	Task=xmlDoc.getElementsByTagName("task");

	var n = xmlDoc.documentElement.childNodes.length;	// the number of tasks. IE gets this right, but mozilla add extra ones (Whitespace)

	for(var i=0;i<n;i++) {

		// optional parameters may not have an entry (Whitespace from mozilla also returns an error )
		// Task ID must NOT be zero other wise it will be skipped
		try { pID = Task[i].getElementsByTagName("pID")[0].childNodes[0].nodeValue;
		} catch (error) {pID =0;}
		pID *= 1;	// make sure that these are numbers rather than strings in order to make jsgantt.js behave as expected.

		if(pID!=0){
			try { pName = Task[i].getElementsByTagName("pName")[0].childNodes[0].nodeValue;
			} catch (error) {pName ="No Task Name";}			// If there is no corresponding entry in the XML file the set a default.

			try { pColor = Task[i].getElementsByTagName("pColor")[0].childNodes[0].nodeValue;
			} catch (error) {pColor ="0000ff";}

			try { pParent = Task[i].getElementsByTagName("pParent")[0].childNodes[0].nodeValue;
			} catch (error) {pParent =0;}
			pParent *= 1;

			try { pStart = Task[i].getElementsByTagName("pStart")[0].childNodes[0].nodeValue;
			} catch (error) {pStart ="";}

			try { pEnd = Task[i].getElementsByTagName("pEnd")[0].childNodes[0].nodeValue;
			} catch (error) { pEnd ="";}

			try { pLink = Task[i].getElementsByTagName("pLink")[0].childNodes[0].nodeValue;
			} catch (error) { pLink ="";}

			try { pMile = Task[i].getElementsByTagName("pMile")[0].childNodes[0].nodeValue;
			} catch (error) { pMile=0;}
			pMile *= 1;

			try { pRes = Task[i].getElementsByTagName("pRes")[0].childNodes[0].nodeValue;
			} catch (error) { pRes ="";}

			try { pComp = Task[i].getElementsByTagName("pComp")[0].childNodes[0].nodeValue;
			} catch (error) {pComp =0;}
			pComp *= 1;

			try { pGroup = Task[i].getElementsByTagName("pGroup")[0].childNodes[0].nodeValue;
			} catch (error) {pGroup =0;}
			pGroup *= 1;

			try { pOpen = Task[i].getElementsByTagName("pOpen")[0].childNodes[0].nodeValue;
			} catch (error) { pOpen =1;}
			pOpen *= 1;

			try { pDepend = Task[i].getElementsByTagName("pDepend")[0].childNodes[0].nodeValue;
			} catch (error) { pDepend =0;}
			//pDepend *= 1;
			if (pDepend.length==0){pDepend=''} // need this to draw the dependency lines

			try { pCaption = Task[i].getElementsByTagName("pCaption")[0].childNodes[0].nodeValue;
			} catch (error) { pCaption ="";}


			// Finally add the task
			pGanttVar.AddTaskItem(new JSGantt.TaskItem(pID , pName, pStart, pEnd, pColor,  pLink, pMile, pRes,  pComp, pGroup, pParent, pOpen, pDepend,pCaption));
		}
	}
};

/**
 * Load an XML document in Chrome
 *
 * @method ChromeLoadXML
 * @param ThisFile {String} - URL to XML file
 * @param pGanttVar {Gantt} - Gantt object
 * @return {void}
 */
JSGantt.ChromeLoadXML = function(ThisFile,pGanttVar){
// Thanks to vodobas at mindlence,com for the initial pointers here.
	XMLLoader = new XMLHttpRequest();
	XMLLoader.onreadystatechange= function(){
		JSGantt.ChromeXMLParse(pGanttVar);
	};
	XMLLoader.open("GET", ThisFile, false);
	XMLLoader.send(null);
};

/**
 * Parse XML document in Chrome
 *
 * @method ChromeXMLParse
 * @param pGanttVar {Gantt} - Gantt object
 * @return {void}
 */

JSGantt.ChromeXMLParse = function (pGanttVar){
// Manually parse the file as it is loads quicker
	if (XMLLoader.readyState == 4) {
		var ta=XMLLoader.responseText.split(/<task>/gi);

		var n = ta.length;	// the number of tasks.
		for(var i=1;i<n;i++) {
			Task = ta[i].replace(/<[/]p/g, '<p');
			var te = Task.split(/<pid>/i);

			if(te.length> 2){var pID=te[1];} else {var pID = 0;}
			pID *= 1;

			var te = Task.split(/<pName>/i);
			if(te.length> 2){var pName=te[1];} else {var pName = "No Task Name";}

			var te = Task.split(/<pstart>/i);
			if(te.length> 2){var pStart=te[1];} else {var pStart = "";}

			var te = Task.split(/<pEnd>/i);
			if(te.length> 2){var pEnd=te[1];} else {var pEnd = "";}

			var te = Task.split(/<pColor>/i);
			if(te.length> 2){var pColor=te[1];} else {var pColor = '0000ff';}

			var te = Task.split(/<pLink>/i);
			if(te.length> 2){var pLink=te[1];} else {var pLink = "";}

			var te = Task.split(/<pMile>/i);
			if(te.length> 2){var pMile=te[1];} else {var pMile = 0;}
			pMile  *= 1;

			var te = Task.split(/<pRes>/i);
			if(te.length> 2){var pRes=te[1];} else {var pRes = "";}

			var te = Task.split(/<pComp>/i);
			if(te.length> 2){var pComp=te[1];} else {var pComp = 0;}
			pComp  *= 1;

			var te = Task.split(/<pGroup>/i);
			if(te.length> 2){var pGroup=te[1];} else {var pGroup = 0;}
			pGroup *= 1;

			var te = Task.split(/<pParent>/i);
			if(te.length> 2){var pParent=te[1];} else {var pParent = 0;}
			pParent *= 1;

			var te = Task.split(/<pOpen>/i);
			if(te.length> 2){var pOpen=te[1];} else {var pOpen = 1;}
			pOpen *= 1;

			var te = Task.split(/<pDepend>/i);
			if(te.length> 2){var pDepend=te[1];} else {var pDepend = "";}
			//pDepend *= 1;
			if (pDepend.length==0){pDepend=''} // need this to draw the dependency lines

			var te = Task.split(/<pCaption>/i);
			if(te.length> 2){var pCaption=te[1];} else {var pCaption = "";}

			// Finally add the task
			pGanttVar.AddTaskItem(new JSGantt.TaskItem(pID , pName, pStart, pEnd, pColor,  pLink, pMile, pRes,  pComp, pGroup, pParent, pOpen, pDepend,pCaption));
		};
	};
};
/**
 * Used for benchmarking performace
 *
 * @method benchMark
 * @param pItem {TaskItem} - TaskItem object
 * @return {void}
 */
JSGantt.benchMark = function(pItem){
	var vEndTime=new Date().getTime();
	alert(pItem + ': Elapsed time: '+((vEndTime-vBenchTime)/1000)+' seconds.');
	vBenchTime=new Date().getTime();
};